package com.leyou.search.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.controller.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.responsitory.GoodsRepository;
import com.leyou.search.service.SearchService;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    // 该方法把一个Spu转为一个Goods对象
    @Override
    public Goods buildGoods(SpuDTO spu) {
// 1 商品相关搜索信息的拼接：名称、分类、品牌、规格信息等
        //查询分类信息
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(spu.getCategoryIds());
        //先转成流,再收集数据(获取name值),再对数据进行编辑,将集合拼接为字符串
        String categoryDtoString = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining(","));
        //查询品牌
        BrandDTO brandDTO = itemClient.queryBrandById(spu.getBrandId());
        //对索引库all字段,进行拼接
        String all = brandDTO.getName() + spu.getName() + categoryDtoString;
        /*==================================================================================*/
        //获取sku中的数据,因为只需要展示sku中的一部分数据,可以使用Map集合来操作
        List<SkuDTO> skuDTOList = itemClient.querySkuBySpuId(spu.getId());
        List<Map<String, Object>> skuMap = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            map.put("price", skuDTO.getPrice());
            map.put("title", skuDTO.getTitle());
            //sku图片只需要显示一张,做截取,只要第一张
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuMap.add(map);
        }
        //转为skus字符串存进索引库
        String skus = JsonUtils.toString(skuMap);

        //获取价格并去重
        Set<Long> priceSet = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());
        /*=============================================================================================*/
        // 获取spu的规格参数,先准备map集合,把编辑好的数据存入该集合
        Map<String, Object> specs = new HashMap<>();
        //  获取规格参数k，根据3级分类查找规格的名称tb_spec_param表
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, spu.getCid3(), true);
        //  获取规格参数的值value，来自于tb_spu_Detail
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailById(spu.getId());
        //获取通用规格值格式如下:k就是对应参数的名称如(1--品牌)specParams中的id值!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // {"1":"华为","2":"G9青春版（全网通版）","3":2016,"5":143,"6":"陶瓷","7":"Android","8":"骁龙（Snapdragon)","9":"骁龙617（msm8952）","10":"八核","11":1.5,"14":5.2,"15":"1920*1080(FHD)","16":800,"17":1300,"18":3000}
        //  获取通用规格参数值,并转为map对象
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        //获取特有规格值格式如下
        //{"4":["白色","金色","玫瑰金"],"12":["3GB"],"13":["16GB"]}
        // 获取特有规格参数值,并转为Map<Long, List<String>>格式
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        for (SpecParamDTO specParam : specParams) {
            String key = specParam.getName();
            Object value = null;
            if (specParam.getGeneric()) {
                value = genericSpec.get(specParam.getId());
            } else {
                value = specialSpec.get(specParam.getId());
            }

            if (specParam.getIsNumeric()) {
                // 是数字类型，分段
                value = chooseSegment(value, specParam);
            }
            specs.put(key, value);
        }


        Goods goods = new Goods();
        // 从spu对象中拷贝与goods对象中属性名一致的属性
        goods.setBrandId(spu.getBrandId());
        goods.setCategoryId(spu.getCid3());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setCreateTime(spu.getCreateTime().getTime());
        goods.setSkus(skus);// spu下的所有sku的JSON数组
        goods.setSpecs(specs); // 当前spu的规格参数
        goods.setPrice(priceSet); // 当前spu下所有sku的价格的集合
        goods.setAll(all);// 商品相关搜索信息的拼接：标题、分类、品牌、规格信息等
        return goods;
    }


    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Override
    public PageResult<GoodsDTO> searchGoods(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(LyExceptionEnum.INVALID_PARAM_ERROR);
        }
//构建查询对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        QueryBuilder basicQuery = basicQuery(request);
        //组装查询条件==匹配查询=====输入华为手机,华为和手机是and关系
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        // 2.2.分页条件,注意此时的Page从0开始
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        //开始查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //将结果封装进PageResult<GoodsDTO>中
        long total = result.getTotalElements();//从result获取总条数
        int totalPages = result.getTotalPages();//result获取总页数
        if (page>totalPages){
            page=1;
        }
        List<Goods> goodsList = result.getContent();//goods的集合

        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        return new PageResult<GoodsDTO>(total, totalPages, goodsDTOList);
    }

    private QueryBuilder basicQuery(SearchRequest request) {

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        //构建基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //构建过滤查询条件--先获取条件
        Map<String, Object> filters = request.getFilters();
        //遍历获取条件的值
        for (String k : filters.keySet()) {
            Object obj = filters.get(k);
            if ("品牌".equals(k)) {
                k = "brandId";
            } else if ("分类".equals(k)) {
                k = "categoryId";
            } else {
                k = "specs." + k;
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(k, obj));
        }
        return boolQueryBuilder;
    }

    @Override
    public Map<String, List<?>> searchFiltrs(SearchRequest request) {
        //先构建map结合,用来封装数据,要有序的Map集合
        HashMap<String, List<?>> map = new LinkedHashMap<>();
        //构建查询对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        QueryBuilder basicQuery = basicQuery(request);
        //组装查询条件==匹配查询=====输入华为手机,华为和手机是and关系
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段,此时不需要过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(null, null));
        // 2.2.分页条件,无需分页查询,但是size不可以为0,因此需要给一个1,不影响查询速度.
        queryBuilder.withPageable(PageRequest.of(0, 1));
        /*-------------------------------------------------------------------------------*/
        //构建聚合条件
        //3.1 品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //3.1 分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //3.2执行查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
//解析品牌聚合结果
        Aggregations aggregations = result.getAggregations();
        Terms brandTerms = aggregations.get(brandAgg);

        handleBrandAgg(brandTerms, map);

        //解析分类聚合结果

        Terms categoryTerms = aggregations.get(categoryAgg);

        List<CategoryDTO> categoryDTOList = handlecategoryAgg(categoryTerms, map);
        if (categoryDTOList != null && categoryDTOList.size() == 1) {
            //调用自定义的方法
            handleSpecAgg(categoryDTOList, map, request);
        }

        return map;
    }
//通过商品的上架,同时在索引库中新增商品
    @Override
    public void saveGoodsToIndex(Long id) {
        SpuDTO spuDTO = itemClient.querySpuBySpuId(id);
        Goods goods = buildGoods(spuDTO);
        goodsRepository.save(goods);
    }
    //通过商品的下架,同时在索引库中删除该商品
    @Override
    public void delteGoodsFromIndex(Long id) {
       goodsRepository.deleteById(id);
    }

    //自定义方法处理
    private void handleSpecAgg(List<CategoryDTO> categoryDTOList, HashMap<String, List<?>> map, SearchRequest request) {
        //构建查询对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        QueryBuilder basicQuery = basicQuery(request);
        //组装查询条件==匹配查询=====输入华为手机,华为和手机是and关系
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段,此时不需要过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(null, null));
        // 2.2.分页条件,无需分页查询,但是size不可以为0,因此需要给一个1,不影响查询速度.
        queryBuilder.withPageable(PageRequest.of(0, 1));
        /*构建聚合条件*/
        Long cid = categoryDTOList.get(0).getId();
        //查询该分类下的规格名称
        List<SpecParamDTO> specParamDTOList = itemClient.querySpecParams(null, cid, true);
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            String name = specParamDTO.getName();

            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            String name = specParamDTO.getName();
            Terms specTerm = aggregations.get(name);
            List<String> termlist = specTerm.getBuckets().stream().map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank).collect(Collectors.toList());
            map.put(name, termlist);
        }

    }

    private List<CategoryDTO> handlecategoryAgg(Terms categoryTerms, HashMap<String, List<?>> map) {
        List<Long> categoryIds = categoryTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(categoryIds);
        map.put("分类", categoryDTOList);
        return categoryDTOList;

    }

    private void handleBrandAgg(Terms brandTerms, HashMap<String, List<?>> map) {

        List<Long> brandIds = brandTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        List<BrandDTO> brandDTOList = itemClient.queryByBrandIds(brandIds);
        map.put("品牌", brandDTOList);

    }
}
